#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dlfcn.h>

#include "JIT.h"
#include "../BMC.h"
#include "../HMC.h"
#include "../Context/ExeContext.h"

void JIT_Init(void){
    
}

//static const char* CreateCFileFCMD   = "echo \"%s\" | sed -e 's/\\\"/\"/g' > dynlib/nes_%04X_pcc.c\0";
static const char* CreateCFileFCMD   = "echo \"%s\" > dynlib/nes_%04X_pcc.c\0";
static const char* CompileBlockFileFCMD = "gcc -fverbose-asm -fPIC -xc -c dynlib/nes_%04X_pcc.c -o dynlib/nes_%04X_pcc.o -g\0";
static const char* CompileBlockFCMD = "echo \"%s\" | gcc -O3 -fverbose-asm -fPIC -xc -c - -o dynlib/nes_%04X_pcc.o\0";
static const char* LinkBlockFCMD    = "gcc -shared dynlib/nes_%04X_pcc.o -o dynlib/nes_%04X_pcc.so\0";

void JIT_CompileBlockF(char* Code, BMC* Block, uint32_t Flags){

    FILE* CompilePipe;
    
    char *CompileCMD, *CreateFileCMD;
    char LinkCMD[0x100];
    uint32_t retVal;
    
    CompileCMD = calloc(strlen(Code) + 0x100, 1);
    
    if(Flags & JITCF_PIPE)
        sprintf(CompileCMD, CompileBlockFCMD, Code, Block->StartAddr);
    else{
        CreateFileCMD = calloc(strlen(Code) + 0x100, 1);
        
        sprintf(CreateFileCMD, CreateCFileFCMD, Code, Block->StartAddr);
        sprintf(CompileCMD, CompileBlockFileFCMD, Block->StartAddr, Block->StartAddr);
        
        retVal = system(CreateFileCMD);
    }
    
    sprintf(LinkCMD, LinkBlockFCMD, Block->StartAddr, Block->StartAddr);
    
    CompilePipe = popen(CompileCMD, "r");

    pclose(CompilePipe);
    
    retVal = system(LinkCMD);
    
    free(CompileCMD);
    
    if(!(Flags & JITCF_PIPE))
        free(CreateFileCMD);
}

static const char* CreateCFileOPTCMD   = "echo \"%s\" > dynlib/nes_%04X_%08lX_ctx.c\0";
static const char* CompileBlockFileOPTCMD = "gcc -fverbose-asm -fPIC -xc -c dynlib/nes_%04X_%08lX_ctx.c -o dynlib/nes_%04X_%08lX_ctx.o -g\0";
static const char* CompileBlockOPTCMD = "echo \"%s\" | gcc -O3 -fverbose-asm -fPIC -xc -c - -o dynlib/nes_%04X_%08lX_ctx.o\0";
static const char* LinkBlockOPTCMD    = "gcc -shared dynlib/nes_%04X_%08lX_ctx.o -o dynlib/nes_%04X_%08lX_ctx.so\0";

void JIT_CompileBlockOPT(char* Code, BMC* Block, uint32_t Flags, ExeContext* Context){

    FILE* CompilePipe;
    
    char *CompileCMD, *CreateFileCMD;
    char LinkCMD[0x100];
    uint32_t retVal;
    
    CompileCMD = calloc(strlen(Code) + 0x100, 1);
    
    if(Flags & JITCF_PIPE)
        sprintf(CompileCMD, CompileBlockOPTCMD, Code, Block->StartAddr, Context->ECXContextHash);
    else{
        CreateFileCMD = calloc(strlen(Code) + 0x100, 1);
        
        sprintf(CreateFileCMD, CreateCFileOPTCMD, Code, Block->StartAddr, Context->ECXContextHash);
        sprintf(CompileCMD, CompileBlockFileOPTCMD, Block->StartAddr, Context->ECXContextHash, Block->StartAddr, Context->ECXContextHash);
        
        retVal = system(CreateFileCMD);
    }
    
    sprintf(LinkCMD, LinkBlockOPTCMD, Block->StartAddr, Context->ECXContextHash, Block->StartAddr, Context->ECXContextHash);
    
    CompilePipe = popen(CompileCMD, "r");

    pclose(CompilePipe);
    
    retVal = system(LinkCMD);
    
    free(CompileCMD);
    
    if(!(Flags & JITCF_PIPE))
        free(CreateFileCMD);
}

#define CHECK_DL_ERROR \
ErrStr = dlerror(); \
if(ErrStr){ \
        printf("ERROR: %s\n", ErrStr); \
        exit(1); \
    }

HMC* JIT_DynamicLink(char* Filename, char* FunctionName){
    HMC* NewHMC;
    void* DlinkHandle;
    char *ErrStr;
    
    NewHMC = HMC_New();
    
    NewHMC->DlinkHandle = dlopen(Filename, RTLD_LAZY | RTLD_GLOBAL);
    
    CHECK_DL_ERROR
            
    NewHMC->BlockCode = dlsym(NewHMC->DlinkHandle, FunctionName);
    
    CHECK_DL_ERROR
            
    return NewHMC;
}

char* JIT_GetDynLibName(BMC* Block, const char* Prefix, const char* Suffix, uint64_t ContextHash){
    char* LibName;
    
    LibName = calloc(0x80,1);
    
    if(ContextHash)
        sprintf(LibName, "%snes_%04X_%08lX_%s.so", Prefix, Block->StartAddr, ContextHash, Suffix);
    else
        sprintf(LibName, "%snes_%04X_%s.so", Prefix, Block->StartAddr, Suffix);
    
    return LibName;
}